Skip to content

fix(editor): restore cursor position after tag/env-var completion in code editors#3406

Merged
waleedlatif1 merged 10 commits intostagingfrom
waleedlatif1/fix-tag-cursor-jump
Mar 4, 2026
Merged

fix(editor): restore cursor position after tag/env-var completion in code editors#3406
waleedlatif1 merged 10 commits intostagingfrom
waleedlatif1/fix-tag-cursor-jump

Conversation

@waleedlatif1
Copy link
Collaborator

Summary

  • Fix cursor jumping to end of code editor after selecting a <> tag or {{}} env var from dropdown
  • Affects all react-simple-code-editor consumers: code sub-block, condition input, and subflow editor
  • Reads live cursor/value from DOM before insertion, computes correct position after the inserted tag, and sets it via selectionStart/selectionEnd

Type of Change

  • Bug fix

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link

vercel bot commented Mar 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Mar 4, 2026 6:09am

Request Review

@waleedlatif1 waleedlatif1 force-pushed the waleedlatif1/fix-tag-cursor-jump branch from 4c3f206 to b3d3312 Compare March 4, 2026 01:59
@waleedlatif1
Copy link
Collaborator Author

@cursor review

@waleedlatif1
Copy link
Collaborator Author

@greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR fixes cursor jumping to the end of react-simple-code-editor instances after selecting a <tag> or {{env-var}} from a dropdown. The fix inverts the responsibility for cursor-position computation: instead of having the consumer (handler in code.tsx, condition-input.tsx, use-subflow-editor.ts) attempt to re-derive the cursor position by searching for delimiters in the post-insertion DOM value, the producer (the dropdown's selection handler) now pre-computes the exact target offset and passes it as a second argument to onSelect. A new shared utility restoreCursorAfterInsertion wraps the setTimeout(0) + selectionStart/selectionEnd pattern so React's controlled-component commit can flush before the cursor is moved.

Key changes:

  • utils.ts — New restoreCursorAfterInsertion(textarea, newCursorPosition) utility replaces the previous multi-file, delimiter-scanning cursor restoration logic.
  • tag-dropdown.tsx — Computes newCursorPos = insertStart + 1 + processedTag.length + 1 (position immediately after the closing >) and passes it via the updated onSelect signature.
  • env-var-dropdown.tsx — Computes tagLength = 2 + envVar.length + 2 (length of {{envVar}}) and passes the correct absolute position via onSelect. Also removes a duplicate dead if/else where both branches were identical.
  • code.tsx, condition-input.tsx, use-subflow-editor.ts — Updated to forward the pre-computed cursor position and call restoreCursorAfterInsertion. CSS.escape is now applied to blockId in condition-input.tsx's querySelector, addressing a latent selector injection risk.
  • One minor documentation inaccuracy: the JSDoc in utils.ts calls setTimeout(0) a "microtask" when it is actually a macrotask (the distinction is intentional and correct, just misdocumented).

Confidence Score: 4/5

  • This PR is safe to merge — the fix is well-scoped, the cursor math is correct in all code paths, and the approach is simpler and more reliable than the previous delimiter-scanning strategy.
  • The pre-computed cursor position is verifiably correct for all tag and env-var insertion cases (both when a partial trigger exists and when inserting from scratch). The shared utility is minimal and handles null gracefully. The only finding is a minor JSDoc inaccuracy ("microtask" vs "macrotask") that has no runtime impact. No tests were added, but the change is straightforward enough that manual testing is reasonable.
  • No files require special attention.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/utils.ts New utility function for cursor restoration after dropdown insertion. Logic is correct — setTimeout(0) is the right tool here — but the JSDoc comment incorrectly calls it a "microtask".
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx Pre-computes cursor position after tag insertion using insertStart + 1 + processedTag.length + 1 and passes it through the onSelect callback. Math is correct for both the "no < found" and "< found" code paths.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/env-var-dropdown.tsx Adds tagLength pre-computation and passes cursor position through onSelect. Also cleans up the dead if/else (both branches were identical). Cursor math is correct for both the standard-context and fallback paths.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/code/code.tsx handleTagSelect and handleEnvVarSelect updated to accept pre-computed cursor position and call restoreCursorAfterInsertion. The isPreview/readOnly guard is also correctly handled with a plain focus-only fallback.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx handleTagSelectImmediate and handleEnvVarSelectImmediate now accept and pass through newCursorPosition; CSS.escape is correctly applied to blockId in querySelector. Cursor restoration logic looks sound.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-subflow-editor.ts handleSubflowTagSelect simplified to accept pre-computed cursor position and call restoreCursorAfterInsertion with textareaRef.current. cursorPosition removed from dependency array, resolving the unnecessary recreation concern from previous threads.

Sequence Diagram

sequenceDiagram
    participant User
    participant Dropdown as TagDropdown / EnvVarDropdown
    participant Handler as handleTagSelect / handleEnvVarSelect
    participant React as React (setState)
    participant Utils as restoreCursorAfterInsertion
    participant DOM as Textarea DOM

    User->>Dropdown: Clicks / presses Enter on item
    Dropdown->>Dropdown: Compute newValue & newCursorPos<br/>(insertStart + tag length)
    Dropdown->>Handler: onSelect(newValue, newCursorPos)
    Handler->>React: setState / setCode(newValue)
    Handler->>Utils: restoreCursorAfterInsertion(textarea, newCursorPos)
    Note over Utils: setTimeout(0) — macrotask
    React-->>DOM: Re-render commits new value
    Utils->>DOM: textarea.focus()<br/>selectionStart = newCursorPos<br/>selectionEnd = newCursorPos
    DOM-->>User: Cursor at correct position
Loading

Last reviewed commit: a73bd6c

@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Move restoreCursorAfterInsertion inside the !isPreview && !readOnly guard
so cursor position isn't computed against newValue when the textarea still
holds liveValue. Add comment documenting the cross-string index invariant
in the shared helper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@waleedlatif1
Copy link
Collaborator Author

@greptile

Prevents potential SyntaxError if blockId ever contains CSS special
characters when querying the textarea for cursor restoration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@waleedlatif1
Copy link
Collaborator Author

@greptile

Replace cursorPosition state in handleSubflowTagSelect's dependency
array with a cursorPositionRef. This avoids recreating the callback
on every keystroke since cursorPosition is only used as a fallback
when textareaRef.current is null.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Instead of inferring cursor position by searching for delimiters in the
output string (which could match unrelated < or {{ in code), compute
the exact cursor position in TagDropdown and EnvVarDropdown where the
insertion range is definitively known, and pass it through onSelect.

This follows the same pattern used by CodeMirror, Monaco, and
ProseMirror: the insertion source always knows the range, so cursor
position is computed at the source rather than inferred by the consumer.

- TagDropdown/EnvVarDropdown: compute newCursorPosition, pass as 2nd arg
- restoreCursorAfterInsertion: simplified to just (textarea, position)
- code.tsx, condition-input.tsx, use-subflow-editor.ts: accept position
- Removed editorValueRef and cursorPositionRef from use-subflow-editor
  (no longer needed since dropdown computes position)
- Other consumers (native inputs) unaffected due to TS callback compat

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@waleedlatif1 waleedlatif1 merged commit 4606598 into staging Mar 4, 2026
6 checks passed
@waleedlatif1 waleedlatif1 deleted the waleedlatif1/fix-tag-cursor-jump branch March 4, 2026 06:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant